// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: daniel

#include "coin2/exchange/bitbank/api_order/native_private_client.h"

#include <list>
#include <utility>

#include "coin2/exchange/base/api_util/url_params_builder.h"
#include "coin2/exchange/base/order/order.h"
#include "coin2/exchange/bitbank/api_util/auth_key.h"
#include "coin2/exchange/bitbank/symbology/product.h"
#include "coin2/exchange/order/bitbank.pb.h"

BitbankNativePrivateClient::BitbankNativePrivateClient(
    const MarketExchangeApi& mea,
    const NativeClientConfig& config) {
  mea_ = mea;
  NativeClientConfig cfg;
  cfg.set_rest_host("api.bitbank.cc");
  cfg.set_rest_host_port(443);
  cfg.MergeFrom(config);
  LOG(INFO) << "[BitbankNativePrivateClient] config: " << cfg.DebugString();
  auths_.Construct<BitbankRestAuth, NativeClientConfig>(cfg);
}

auto BitbankNativePrivateClient::QueryAccountBalance() -> HttpContextPtr {
  UrlParamsBuilder builder;
  auto context = GetAuth()->CreateRequestByGetWithSignature(path_query_account_balance_, &builder);

  context->query_type = OrderSubTopicId::REST_ACCOUNT_BALANCE;
  return HttpContextPtr(context);
}

auto BitbankNativePrivateClient::QueryOrders(const RestParams& params) -> HttpContextPtr {
  if (!params.has_pair()) {
    LOG(ERROR) << "[QueryOrders] missing mandatory parameter: pair. " << params.DebugString();
    return {};
  }
  UrlParamsBuilder builder;
  builder.PutGet("pair", params.pair());
  auto context = GetAuth()->CreateRequestByGetWithSignature(path_query_orders_, &builder);

  context->query_type = OrderSubTopicId::REST_OPEN_ORDERS;
  return HttpContextPtr(context);
}

auto BitbankNativePrivateClient::QueryTrades(const RestParams& params) -> HttpContextPtr {
  if (!params.has_pair()) {
    LOG(ERROR) << "[QueryTrades] missing mandatory parameter: pair. " << params.DebugString();
    return {};
  }
  UrlParamsBuilder builder;
  builder.PutGet("pair", params.pair());
  auto context = GetAuth()->CreateRequestByGetWithSignature(path_query_trades_, &builder);

  context->query_type = OrderSubTopicId::REST_FILL_ORDERS;
  return HttpContextPtr(context);
}

auto BitbankNativePrivateClient::QueryOrderInfo(const RestParams& params) -> HttpContextPtr {
  if (!params.has_pair() || !params.has_order_id()) {
    LOG(ERROR) << "[QueryOrderInfo] missing mandatory parameter: pair/order_id. "
               << params.DebugString();
    return {};
  }
  UrlParamsBuilder builder;
  builder.PutGet("pair", params.pair());
  builder.PutGet("order_id", params.order_id());
  auto context = GetAuth()->CreateRequestByGetWithSignature(path_query_order_info_, &builder);

  context->query_type = OrderSubTopicId::REST_ORDERS_INFO;
  return HttpContextPtr(context);
}

auto BitbankNativePrivateClient::QueryOrdersInfo(const RestParams& params) -> HttpContextPtr {
  if (!params.has_pair() || params.order_ids().size() == 0) {
    LOG(ERROR) << "[QueryOrdersInfo] missing mandatory parameter: pair/order_ids. "
               << params.DebugString();
    return {};
  }
  UrlParamsBuilder builder;
  std::list<std::string> order_ids;
  for (auto&& order_id : params.order_ids()) {
    order_ids.push_back(order_id);
  }
  builder.PutPost("pair", params.pair());
  builder.PutPost<true>("order_ids", std::move(order_ids));
  auto context = GetAuth()->CreateRequestByPostWithSignature(path_query_orders_info_, &builder);

  context->query_type = OrderSubTopicId::REST_ORDERS_INFO;
  return HttpContextPtr(context);
}

auto BitbankNativePrivateClient::PlaceOrder(const RestParams& params) -> HttpContextPtr {
  if (!params.has_pair() || !params.has_side() || !params.has_amount() || !params.has_type()) {
    LOG(ERROR) << "[PlaceOrder] missing mandatory parameter: pair/side/amount/type. "
               << params.DebugString();
  }
  UrlParamsBuilder builder;
  builder.PutPost("pair", params.pair());
  builder.PutPost("side", params.side());
  builder.PutPost("amount", params.amount());
  builder.PutPost("price", params.price());
  builder.PutPost("type", params.type());
  builder.PutPost("post_only", params.post_only());
  auto context = GetAuth()->CreateRequestByPostWithSignature(path_place_order_, &builder);

  context->query_type = OrderSubTopicId::SUBMIT_RESPONSE;
  return HttpContextPtr(context);
}

auto BitbankNativePrivateClient::CancelSingleOrder(const RestParams& params) -> HttpContextPtr {
  UrlParamsBuilder builder;
  builder.PutPost("pair", params.pair());
  builder.PutPost("order_id", params.order_id());
  auto context = GetAuth()->CreateRequestByPostWithSignature(path_cancel_single_order_, &builder);

  context->query_type = OrderSubTopicId::CANCEL_RESPONSE;
  return HttpContextPtr(context);
}

auto BitbankNativePrivateClient::CancelBatchOrders(const RestParams& params) -> HttpContextPtr {
  UrlParamsBuilder builder;

  std::list<std::string> order_ids(params.order_ids().begin(), params.order_ids().end());

  builder.PutPost("pair", params.pair());
  builder.PutPost<true>("order_ids", order_ids);
  auto context = GetAuth()->CreateRequestByPostWithSignature(path_cancel_batch_orders_, &builder);

  context->query_type = OrderSubTopicId::MULTI_CANCEL_RESPONSE;
  return HttpContextPtr(context);
}

auto BitbankNativePrivateClient::PlaceOrderNorm(const OrderExecution& order_exe) -> HttpContextPtr {
  const auto& order_spec = *order_exe.order_spec;
  const auto proc_order_id = order_exe.proc_order_id;
  auto params = GetRestOrderParams(order_spec, proc_order_id);
  return PlaceOrder(params);
}

auto BitbankNativePrivateClient::CancelOrderNorm(const OrderExecution& order_exe)
    -> HttpContextPtr {
  if (!order_exe.is_cancel_batch_orders) {
    auto params = GetRestCancelParams(*order_exe.oc);
    return CancelSingleOrder(params);
  }

  auto mutable_order_exe = const_cast<OrderExecution*>(&order_exe);

  if (mutable_order_exe->cancel_pending_order_deque.empty()) {
    return {};
  }

  OrderExecution::OrderContextList temp_cancel_pending_order_deque;
  const auto& symbol = mutable_order_exe->cancel_pending_order_deque.front()->native_product();
  RestParams param;
  param.set_pair(symbol);

  int cnt = 0;
  while (!mutable_order_exe->cancel_pending_order_deque.empty()) {
    const auto* order_context = mutable_order_exe->cancel_pending_order_deque.front();
    mutable_order_exe->cancel_pending_order_deque.pop_front();

    if (order_context->native_product() != symbol || cnt >= 30) {
      temp_cancel_pending_order_deque.emplace_back(order_context);
      continue;
    }

    mutable_order_exe->cancel_proceed_order_deque.emplace_back(order_context);
    ++cnt;

    CHECK(order_context->has_exchange_order_id()) << "bitbank only support cancel by order_id!";

    param.add_order_ids(order_context->exchange_order_id());
  }

  mutable_order_exe->cancel_pending_order_deque = std::move(temp_cancel_pending_order_deque);

  return CancelBatchOrders(param);
}

auto BitbankNativePrivateClient::GetRestOrderParams(
    const OrderSpec& order_spec,
    int64_t proc_order_id) -> RestParams {
  RestParams params;

  using coin2::exchange::bitbank::symbology::BitbankProduct;
  auto product = BitbankProduct::FromStr(order_spec.product, order_spec.order_created_time);
  auto product_holder = GetProductHolder(product, order_spec.order_created_time);
  auto formatter = product_holder->product_info().order_formatter();
  params.set_pair(order_spec.product);
  if (order_spec.order_side == OrderSide::BUY_ORDER) {
    params.set_side("buy");
  } else if (order_spec.order_side == OrderSide::SELL_ORDER) {
    params.set_side("sell");
  } else {
    NOTREACHED() << "Unknown order side: " << static_cast<int>(order_spec.order_side);
  }
  params.set_amount(formatter.FormatQty(order_spec.qty));
  if (order_spec.order_type == OrderType::LIMIT_ORDER) {
    params.set_type("limit");
    params.set_price(formatter.FormatPrice(order_spec.price));
  } else if (order_spec.order_type == OrderType::MARKET_ORDER) {
    params.set_type("market");
  } else {
    NOTREACHED() << "unsupported order type: " << static_cast<int>(order_spec.order_type);
  }
  if (order_spec.post_only) {
    params.set_post_only("True");
  }

  return params;
}

auto BitbankNativePrivateClient::GetRestCancelParams(const IOrderContext& order_context)
    -> RestParams {
  RestParams params;
  params.set_pair(order_context.native_product());
  params.set_order_id(order_context.exchange_order_id());

  return params;
}

std::shared_ptr<AsioHttpContext> BitbankNativePrivateClient::SubmitOrderExecution(
    const OrderExecutionProto& order) {
  switch (order.type()) {
    case OrderExecutionProto::SUBMIT_ORDER: {
      auto order_spec = OrderSpec::FromProto(order.order_spec());
      auto params = GetRestOrderParams(*order_spec, order.proc_order_id());
      return PlaceOrder(params);
    }
    case OrderExecutionProto::CANCEL_ORDER: {
      auto order_spec = OrderSpec::FromProto(order.order_spec());
      auto params = GetRestOrderParams(*order_spec, order.proc_order_id());
      return CancelSingleOrder(params);
    }
    default: {
      return nullptr;
    }
  }
}

std::shared_ptr<AsioHttpContext> BitbankNativePrivateClient::SubmitRestQuery(
    const RestQueryProto& query) {
  RestParams params;
  switch (query.type()) {
    case RestQueryProto::QUERY_ACCOUNT_BALANCE: {
      return QueryAccountBalance();
    }
    case RestQueryProto::QUERY_OPEN_ORDERS: {
      return QueryOrders(params);
    }
    default: {
      return nullptr;
    }
  }
}
